package com.bridgeintelligent.tag.engine.mvc.service.impl;

import com.bridgeintelligent.tag.engine.api.DimItemInfo;
import com.bridgeintelligent.tag.engine.api.EngineException;
import com.bridgeintelligent.tag.engine.api.IndexNameInfo;
import com.bridgeintelligent.tag.engine.api.rsp.DimTop10Rsp;
import com.bridgeintelligent.tag.engine.api.rsp.SetModel;
import com.bridgeintelligent.tag.engine.core.cache.CacheConfig;
import com.bridgeintelligent.tag.engine.core.cache.CacheUtils;
import com.bridgeintelligent.tag.engine.es.base.ESQueryUtils;
import com.bridgeintelligent.tag.engine.es.model.Top10Model;
import com.bridgeintelligent.tag.engine.mvc.service.IDimTop10Service;
import com.bridgeintelligent.tag.engine.mvc.service.IEsDateService;
import com.bridgeintelligent.tag.engine.mvc.service.IMonitorService;
import com.bridgeintelligent.tag.engine.parse.BaseParse;
import com.wayneleo.quickstart.framework.ExceptionCode;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.search.MultiSearchRequest;
import org.elasticsearch.action.search.MultiSearchResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.BucketOrder;
import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramInterval;
import org.elasticsearch.search.aggregations.bucket.histogram.Histogram;
import org.elasticsearch.search.aggregations.bucket.histogram.HistogramAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.range.Range;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.*;

/**
 * @description 统计时间内的top10
 * @author: grp 2020-10-12 13:38
 **/
@Service
@Slf4j
public class DimTop10ServiceImpl implements IDimTop10Service, IMonitorService {

    @Autowired
    private IEsDateService esDateService;

    @Override
    public DimTop10Rsp discrete(Top10Model model) throws IOException {
        List<DimTop10Rsp.Result> data = new ArrayList<>();
        Set<String> titles = getSortTitles(model);
        getResult(model, model.getDates(), data, titles);
        return DimTop10Rsp.of().setData(DimTop10Rsp.R.of().setRecords(data).setTitles(new ArrayList<>(titles)));
    }

    @Override
    public DimTop10Rsp defaultGroup(Top10Model model) throws IOException {
        //如果是集合型标签
        if ("06".equals(model.getType())) {
            return getDateResult(model);
        } else {
            List<DimTop10Rsp.Result> data = new ArrayList<>();
            Set<String> titles = getSortTitles(model);
            getGroupResult(model, model.getDates(), data, titles);
            return DimTop10Rsp.of().setData(DimTop10Rsp.R.of().setRecords(data).setTitles(new ArrayList<>(titles)));
        }
    }


    public DimTop10Rsp getDateResult(Top10Model model){
        //查询ES时间
        Map<String, String> timeMap = esDateService.timeSymbolRelation("1");
        timeMap = Optional.ofNullable(timeMap).orElse(new HashMap<>());
        //先封装多个request
        List<String> dates = model.getDates();
        String dimId = model.getDimId();
        List<DimTop10Rsp.Result> dataList = new ArrayList<>();
        SetModel setModel = treeSet(model.getDimId(),model.getKeys());
        IndexNameInfo indexNameInfo = CacheUtils.getCacheInfo(CacheConfig.CONFIGURATION_INFO, "1");
        MultiSearchRequest request = new MultiSearchRequest();
        for (String date : dates) {
            date = Optional.ofNullable(timeMap.get(date)).orElse(date);
            request.add(getListSearchRequest(indexNameInfo, dimId, date));
        }
        try {
            handleListResponse(model, dates, dataList, setModel.getTitles(), request);
            return DimTop10Rsp.of().setData(DimTop10Rsp.R.of().setRecords(dataList).setTitles(new ArrayList<>(setModel.getNewTitles())));
        } catch (IOException e) {
            log.info("---------查询ES出现异常------------",e);
            throw new EngineException(new ExceptionCode(10002,"查询ES出现异常"),e);
        }
    }

    private SetModel treeSet(String dimId,List<String> keys){
        Set<String> titles = new TreeSet<>(Comparator.naturalOrder());
        Set<String> newTitles = new TreeSet<>(Comparator.naturalOrder());
        keys.stream().forEach(e ->{
            titles.add(e);
            String title = Optional
                    .ofNullable((DimItemInfo) CacheUtils.getCacheInfo(CacheConfig.DIM_ITEM_INFO,
                            String.format(BaseParse.GROUP_FORMAT, dimId, e)))
                    .map(DimItemInfo::getDimItemName)
                    .orElse(e);
            newTitles.add(joinStr(title,e));

        });
        return new SetModel(titles,newTitles);
    }

    private SearchRequest getListSearchRequest(IndexNameInfo indexNameInfo, String dimId, String date) {
        String dimIdColumn = getDimIdColumn(dimId, date);
        BoolQueryBuilder boolQueryBuilder = getBoolQueryBuilder(dimIdColumn);
        TermsAggregationBuilder termsBuilder = AggregationBuilders.terms("count")
                .field(dimIdColumn)
                .order(BucketOrder.key(true))
                .size(Integer.MAX_VALUE);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.aggregation(termsBuilder).query(boolQueryBuilder).trackTotalHits(true).size(0);
        return new SearchRequest(indexNameInfo.getDataDate()).types(indexNameInfo.getIndexType())
                .source(searchSourceBuilder);
    }

    private void handleListResponse(Top10Model model, List<String> dates, List<DimTop10Rsp.Result> dataList, Set<String> titles, MultiSearchRequest request) throws IOException {
        int sort = 0;
        MultiSearchResponse multiSearchResponse =
                ESQueryUtils.getClient().multiSearch(request, RequestOptions.DEFAULT);
        for (MultiSearchResponse.Item item : multiSearchResponse.getResponses()) {
            String date = dates.get(sort++);
            //处理单个response
            SearchResponse response = item.getResponse();
            getListSingleDateData(model, dataList, titles, date, response);
        }
    }

    private void getListSingleDateData(Top10Model model, List<DimTop10Rsp.Result> dataList, Set<String> titles, String date, SearchResponse response) {
        Aggregations aggregations = response.getAggregations();
        Terms terms = aggregations.get("count");
        DimTop10Rsp.Result result = new DimTop10Rsp.Result();
        List<DimTop10Rsp.Result.Info> infos = new ArrayList<>();
        for (Terms.Bucket entry : terms.getBuckets()) {
            if (titles.contains(entry.getKeyAsString())){
                DimTop10Rsp.Result.Info info = new DimTop10Rsp.Result.Info();
                String title = Optional
                        .ofNullable((DimItemInfo) CacheUtils.getCacheInfo(CacheConfig.DIM_ITEM_INFO,
                                String.format(BaseParse.GROUP_FORMAT, model.getDimId(), entry.getKeyAsString())))
                        .map(DimItemInfo::getDimItemName)
                        .orElse(entry.getKeyAsString());
                info.setTitle(joinStr(title,entry.getKeyAsString()));
                info.setCount(entry.getDocCount() + "");
                infos.add(info);
            }
        }

        result.setDate(date);
        result.setInfo(infos);
        dataList.add(result);
    }

    public String joinStr(String key,String value){
        StringBuffer buffer = new StringBuffer();
        buffer.append("").append(key).append("(").append(value).append(")");
        return buffer.toString();
    }

    @Override
    public DimTop10Rsp customize(Top10Model model) throws IOException {
        List<DimTop10Rsp.Result> data = new ArrayList<>();
        Set<String> titles = getSortTitles(model);
        getCustomizeResult(model, model.getDates(), data, titles);
        return DimTop10Rsp.of().setData(DimTop10Rsp.R.of().setRecords(data).setTitles(new ArrayList<>(titles)));
    }

    private Set<String> getSortTitles(Top10Model model) {
        if (StringUtils.equals("03", model.getType())) {
            // 默认升序
            return new TreeSet<>((D1, D2) -> {
                Double v1 = getValue(D1);
                Double v2 = getValue(D2);
                return v1.compareTo(v2);
            });
        }
        return new TreeSet<>(Comparator.naturalOrder());
    }

    /**
     * 批量获取结果
     *
     * @param model  查询条件
     * @param dates  查询时间列表
     * @param data   结果集
     * @param titles title名称列表
     * @throws IOException es异常
     */
    private void getResult(Top10Model model, List<String> dates, List<DimTop10Rsp.Result> data, Set<String> titles) throws IOException {

        Map<String, String> timeMap = esDateService.timeSymbolRelation("1");
        timeMap = Optional.ofNullable(timeMap).orElse(new HashMap<>());
        IndexNameInfo indexNameInfo = CacheUtils.getCacheInfo(CacheConfig.CONFIGURATION_INFO, "1");
        MultiSearchRequest request = new MultiSearchRequest();
        for (String date : dates) {
            request.add(getSearchRequest(indexNameInfo, model.getDimId(), date,timeMap));
        }
        handleResponse(model, dates, data, titles, request);
    }

    private void getGroupResult(Top10Model model, List<String> dates, List<DimTop10Rsp.Result> data, Set<String> titles) throws IOException {

        Map<String, String> timeMap = esDateService.timeSymbolRelation("1");
        timeMap = Optional.ofNullable(timeMap).orElse(new HashMap<>());
        IndexNameInfo indexNameInfo = CacheUtils.getCacheInfo(CacheConfig.CONFIGURATION_INFO, "1");
        MultiSearchRequest request = new MultiSearchRequest();
        if (StringUtils.equals("03", model.getType())) {
            for (String date : dates) {
                date = Optional.ofNullable(timeMap.get(date)).orElse(date);
                String dimIdColumn = getDimIdColumn(model.getDimId(), date);
                request.add(getNumberSearchRequest(indexNameInfo, dimIdColumn, model.getInterval()));
            }
        }
        if (StringUtils.equals("05", model.getType())) {
            for (String date : dates) {
                date = Optional.ofNullable(timeMap.get(date)).orElse(date);
                String dimId = model.getDimId() + "_" + date;
                request.add(getDateSearchRequest(indexNameInfo, dimId, model.getInterval()));
            }
        }
        handleGroupResponse(model, dates, data, titles, request);
    }

    private void getCustomizeResult(Top10Model model, List<String> dates, List<DimTop10Rsp.Result> data, Set<String> titles) throws IOException {
        //查询ES时间
        Map<String, String> timeMap = esDateService.timeSymbolRelation("1");
        timeMap = Optional.ofNullable(timeMap).orElse(new HashMap<>());
        IndexNameInfo indexNameInfo = CacheUtils.getCacheInfo(CacheConfig.CONFIGURATION_INFO, "1");
        MultiSearchRequest request = new MultiSearchRequest();
        if (StringUtils.equals("03", model.getType())) {
            for (String date : dates) {
                date = Optional.ofNullable(timeMap.get(date)).orElse(date);
                String dimIdColumn = getDimIdColumn(model.getDimId(), date);
                request.add(getCustomizeNumberSearchRequest(indexNameInfo, dimIdColumn, model.getItems()));
            }
        }
        if (StringUtils.equals("05", model.getType())) {
            for (String date : dates) {
                date = Optional.ofNullable(timeMap.get(date)).orElse(date);
                String dimIdColumn = getDimIdColumn(model.getDimId(), date);
                request.add(getCustomizeDateSearchRequest(indexNameInfo, dimIdColumn, model.getItems()));
            }
        }
        handleCustomizeResponse(dates, data, titles, request);
    }

    /**
     * 处理es返回结果
     *
     * @param model   查询输入
     * @param dates   查询时间列表
     * @param data    结果集
     * @param titles  title名称列表
     * @param request 请求实体
     * @throws IOException es异常
     */
    private void handleResponse(Top10Model model, List<String> dates, List<DimTop10Rsp.Result> data, Set<String> titles, MultiSearchRequest request) throws IOException {
        int sort = 0;
        MultiSearchResponse multiSearchResponse =
                ESQueryUtils.getClient().multiSearch(request, RequestOptions.DEFAULT);

        for (MultiSearchResponse.Item item : multiSearchResponse.getResponses()) {
            String date = dates.get(sort++);
            SearchResponse response = item.getResponse();
            getSingleDateData(model, data, titles, date, response);
        }
    }

    private void handleGroupResponse(Top10Model model, List<String> dates, List<DimTop10Rsp.Result> data, Set<String> titles, MultiSearchRequest request) throws IOException {
        int sort = 0;
        MultiSearchResponse multiSearchResponse =
                ESQueryUtils.getClient().multiSearch(request, RequestOptions.DEFAULT);
        for (MultiSearchResponse.Item item : multiSearchResponse.getResponses()) {
            String date = dates.get(sort++);
            SearchResponse response = item.getResponse();
            getGroupSingleDateData(model, data, titles, date, response);
        }
    }

    private void handleCustomizeResponse(List<String> dates, List<DimTop10Rsp.Result> data, Set<String> titles, MultiSearchRequest request) throws IOException {
        int sort = 0;
        MultiSearchResponse multiSearchResponse =
                ESQueryUtils.getClient().multiSearch(request, RequestOptions.DEFAULT);
        for (MultiSearchResponse.Item item : multiSearchResponse.getResponses()) {
            String date = dates.get(sort++);
            SearchResponse response = item.getResponse();
            getCustomizeSingleDateData(data, titles, date, response);
        }
    }

    /**
     * 获取单条统计数据
     *
     * @param model    查询输入
     * @param data     数据集
     * @param titles   title名称列表
     * @param date     统计期
     * @param response 响应
     */
    private void getSingleDateData(Top10Model model, List<DimTop10Rsp.Result> data, Set<String> titles, String date, SearchResponse response) {
        Aggregations aggregations = response.getAggregations();
        Terms terms = aggregations.get("count");
        DimTop10Rsp.Result result = new DimTop10Rsp.Result();
        List<DimTop10Rsp.Result.Info> infos = new ArrayList<>();
        int number = 0;
        for (Terms.Bucket entry : terms.getBuckets()) {
            if (number > 9) {
                break;
            }
            DimTop10Rsp.Result.Info info = new DimTop10Rsp.Result.Info();
            String title = Optional
                    .ofNullable((DimItemInfo) CacheUtils.getCacheInfo(CacheConfig.DIM_ITEM_INFO,
                            String.format(BaseParse.GROUP_FORMAT, model.getDimId(), entry.getKeyAsString())))
                    .map(DimItemInfo::getDimItemName)
                    .orElse(entry.getKeyAsString());
            titles.add(title);
            info.setTitle(title);
            info.setCount(entry.getDocCount() + "");
            number++;
            infos.add(info);
        }

        result.setDate(date);
        result.setInfo(infos);
        data.add(result);
    }

    /**
     * 默认区间
     *
     * @param model    输入
     * @param data     结果集
     * @param titles   title名称列表
     * @param date     日期
     * @param response 响应
     */
    private void getGroupSingleDateData(Top10Model model, List<DimTop10Rsp.Result> data, Set<String> titles, String date, SearchResponse response) {
        Aggregations aggregations = response.getAggregations();
        Histogram terms = aggregations.get("count");
        DimTop10Rsp.Result result = new DimTop10Rsp.Result();
        List<DimTop10Rsp.Result.Info> infos = new ArrayList<>();
        int number = 0;
        for (Histogram.Bucket entry : terms.getBuckets()) {
            if (number > 9) {
                break;
            }
            DimTop10Rsp.Result.Info info = new DimTop10Rsp.Result.Info();
            if (StringUtils.equals("03", model.getType())) {
                long key = ((Double) entry.getKey()).longValue();
                long high = key + ((Double) Double.parseDouble(model.getInterval())).longValue();
                String title = key + "~" + high;
                titles.add(title);
                info.setTitle(title);
            }
            if (StringUtils.equals("05", model.getType())) {
                String title = getTitle(entry.getKeyAsString(), new DateHistogramInterval(model.getInterval()));
                titles.add(title);
                info.setTitle(title);
            }
            info.setCount(entry.getDocCount() + "");
            number++;
            infos.add(info);
        }
        result.setDate(date);
        result.setInfo(infos);
        data.add(result);
    }

    private void getCustomizeSingleDateData(List<DimTop10Rsp.Result> data, Set<String> titles, String date, SearchResponse response) {
        DimTop10Rsp.Result result = new DimTop10Rsp.Result();
        List<DimTop10Rsp.Result.Info> infos = new ArrayList<>();
        if (response != null) {
            int number = 0;
            Aggregations aggregations = response.getAggregations();
            Range terms = aggregations.get("count");
            for (Range.Bucket entry : terms.getBuckets()) {
                if (number > 9) {
                    break;
                }
                DimTop10Rsp.Result.Info info = new DimTop10Rsp.Result.Info();
                String title = entry.getKeyAsString();
                titles.add(title);
                info.setTitle(title);
                info.setCount(entry.getDocCount() + "");
                number++;
                infos.add(info);
            }
        }
        result.setDate(date);
        result.setInfo(infos);
        data.add(result);
    }

    private SearchRequest getSearchRequest(IndexNameInfo indexNameInfo, String dimId, String date,Map<String,String> timeMap) {
        BucketOrder bucketOrder = BucketOrder.count(false);
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        date = Optional.ofNullable(timeMap.get(date)).orElse(date);
        String dimIdColumn = getDimIdColumn(dimId, date);
        boolQueryBuilder.must(
                QueryBuilders.boolQuery()
                        .should(QueryBuilders.boolQuery().must(QueryBuilders.existsQuery(dimIdColumn))
                        ));
        TermsAggregationBuilder termsBuilder =
                AggregationBuilders.terms("count").field(dimIdColumn).order(bucketOrder).size(Integer.MAX_VALUE);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.aggregation(termsBuilder).query(boolQueryBuilder).trackTotalHits(true).size(0);
        return new SearchRequest(indexNameInfo.getDataDate())
                .types(indexNameInfo.getIndexType())
                .source(searchSourceBuilder);
    }

    private SearchRequest getNumberSearchRequest(IndexNameInfo indexNameInfo, String dimId, String interval) {
        BucketOrder bucketOrder = BucketOrder.count(false);
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        boolQueryBuilder.must(
                QueryBuilders.boolQuery()
                        .should(QueryBuilders.boolQuery().must(QueryBuilders.existsQuery(dimId))
                        ));
        HistogramAggregationBuilder builder =
                AggregationBuilders.histogram("count").field(dimId).interval(Double.parseDouble(interval)).order(bucketOrder).minDocCount(1);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.aggregation(builder);
        searchSourceBuilder.query(boolQueryBuilder).trackTotalHits(true).size(0);
        return new SearchRequest(indexNameInfo.getDataDate())
                .types(indexNameInfo.getIndexType())
                .source(searchSourceBuilder);
    }

    private SearchRequest getDateSearchRequest(IndexNameInfo indexNameInfo, String dimId, String interval) {
        BucketOrder bucketOrder = BucketOrder.count(false);
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        boolQueryBuilder.must(
                QueryBuilders.boolQuery()
                        .should(QueryBuilders.boolQuery().must(QueryBuilders.existsQuery(dimId))
                        ));
        DateHistogramAggregationBuilder builder =
                AggregationBuilders.dateHistogram("count").field(dimId).dateHistogramInterval(new DateHistogramInterval(interval)).format(
                        "yyyy-MM-dd").order(bucketOrder).minDocCount(1);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.aggregation(builder);
        searchSourceBuilder.query(boolQueryBuilder).trackTotalHits(true).size(0);
        return new SearchRequest(indexNameInfo.getDataDate())
                .types(indexNameInfo.getIndexType())
                .source(searchSourceBuilder);
    }
}